#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/queue.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

typedef uint32_t u32;
typedef int32_t i32;

struct process {
  u32 pid;
  u32 arrival_time;
  u32 burst_time; 
        
  TAILQ_ENTRY(process) pointers; // this part corresponds to the the pointers part
  /* Additional fields here */
  u32 remaining_time;
  u32 cpu_time;
  u32 flag; //count how many time slices a process have used / how many execution
  u32 waiting_time;
  u32 response_time;
  bool response_time_counted;
  u32 monitor;
  int state; //0 --- waiting,1 --- running, 2--- done,3 ---not arrive;
  /* End of "Additional fields here" */
};

TAILQ_HEAD(process_list, process);//now we can use the newly defined struct:

u32 next_int(const char **data, const char *data_end) {
  u32 current = 0;
  bool started = false;
  while (*data != data_end) {
    char c = **data;

    if (c < 0x30 || c > 0x39) {
      if (started) {
	      return current;
      }
    }
    else {
      if (!started) {
	      current = (c - 0x30);
	      started = true;
      }
      else {
	      current *= 10;
	      current += (c - 0x30);
      }
    }

    ++(*data);
  }

  printf("Reached end of file while looking for another integer\n");
  exit(EINVAL);
}

u32 next_int_from_c_str(const char *data) {
  char c;
  u32 i = 0;
  u32 current = 0;
  bool started = false;//At the beginning, the state is not started
  while ((c = data[i++])) {
    if (c < 0x30 || c > 0x39) {
      exit(EINVAL);
    }
    if (!started) {
      current = (c - 0x30);
      started = true;
    }
    else {
      current *= 10;
      current += (c - 0x30);
    }
  }
  return current;
}

void init_processes(const char *path,
                    struct process **process_data,
                    u32 *process_size)
{
  int fd = open(path, O_RDONLY);
  if (fd == -1) {
    int err = errno;
    perror("open");
    exit(err);
  }

  struct stat st;
  if (fstat(fd, &st) == -1) {
    int err = errno;
    perror("stat");
    exit(err);
  }

  u32 size = st.st_size;
  const char *data_start = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
  if (data_start == MAP_FAILED) {
    int err = errno;
    perror("mmap");
    exit(err);
  }

  const char *data_end = data_start + size;
  const char *data = data_start;
  

  *process_size = next_int(&data, data_end);

  *process_data = calloc(sizeof(struct process), *process_size);
  if (*process_data == NULL) {
    int err = errno;
    perror("calloc");
    exit(err);
  }

  for (u32 i = 0; i < *process_size; ++i) {
    (*process_data)[i].pid = next_int(&data, data_end);
    (*process_data)[i].arrival_time = next_int(&data, data_end);
    (*process_data)[i].burst_time = next_int(&data, data_end);
  }
  
  munmap((void *)data, size);
  close(fd);
}

int main(int argc, char *argv[])
{
  if (argc != 3) {//.rr processes.txt 3 ,total 3
    return EINVAL;
  }
  struct process *data;// load the data of processes.txt with pointer data
  u32 size;
  init_processes(argv[1], &data, &size);//directly use &data[] and size[], size is the number of processes

  u32 quantum_length = next_int_from_c_str(argv[2]); //define quantum length

  struct process_list list;
  TAILQ_INIT(&list);//initialize tailq

  u32 total_waiting_time = 0;
  u32 total_response_time = 0;

  /* Your code here */

  u32 current_time = 0;
  u32 finished_number = 0;
  
  struct process * current_process;

  for(u32 i=0; i<size;++i){// initialize arguments
    current_process = &data[i];
    current_process->remaining_time = current_process->burst_time;
    current_process->cpu_time = 0;
    current_process->flag=0;
    current_process->response_time_counted=false;
    current_process->state=3;
  }

  bool if_finished = false;
  bool handover = false;

  struct process *begin_time = &data[0];
  u32 start_time = begin_time->arrival_time;
  u32 mediator; //When the time slice ends and insertion occurs simultaneously

  while(!if_finished){
    if(finished_number == size - 1){
      if_finished =true;
    }

    for(u32 i=0; i<size;++i){
      struct process *insert = &data[i];
      if (insert->arrival_time == current_time){
        insert->state=0;
        if (handover==true){
          TAILQ_INSERT_BEFORE(&data[mediator],&data[i],pointers);
        }
        else if(handover == false){
          TAILQ_INSERT_TAIL(&list,&data[i],pointers);
        }
      }
    }

    for(u32 i=0; i<size;++i){
      current_process =&data[i];
      if (current_process->arrival_time <= start_time){
        start_time=current_process->arrival_time;
      }
    }
    
    //response to the first process in queque
    if (start_time <= current_time){

      struct process * temp_element = TAILQ_FIRST(&list);
      u32 getpid = temp_element->pid - 1 ;
      current_process = &data[getpid];
      current_process->state=1;
      handover=false;

      printf("\n --- time  unit %d now start --- \n  \n    Current process ID is %d \n", current_time,current_process->pid);
      //response time caculator
      while(!current_process->response_time_counted){
          current_process->response_time_counted =true;
          current_process->response_time = current_time -current_process->arrival_time;
        }
      //waiting time calculator
      for(u32 i=0; i<size;++i){
          struct process *calculator =&data[i];
          if(calculator->state==0){
            calculator->waiting_time +=1;
          }
      }

      if (current_process->cpu_time + 1 ==current_process->burst_time){//will end in this unit
        finished_number++;
        current_process->state = 2 ;
        current_process->cpu_time++;
        current_process->remaining_time--;
        current_process->flag = current_process->flag +1;
        printf(" Process %d terminated succesfully! \n",current_process->pid);
        struct process * current_element;
        struct process * next_element;
        for((current_element=TAILQ_FIRST(&list)); current_element!=NULL;current_element = next_element){
			    next_element = TAILQ_NEXT(current_element, pointers);
          TAILQ_REMOVE(&list,&data[getpid],pointers);
        }
      }
      else if ( current_process->cpu_time + 1 < current_process->burst_time) { //will not end in this unit (1)keep going (2)preempt
        current_process->cpu_time++;
        current_process->remaining_time--;
        current_process->monitor++;
        if(current_process->monitor  == quantum_length ){// handover start
          handover = true;
          mediator = getpid;//they will be used only if new arrival and preemption occur at same time
          current_process->flag = current_process->flag + 1;
          current_process->state = 0; //at the end of this unit state of current process turns into waiting from running
          current_process->monitor = 0;
          struct process * current_element;
          struct process * next_element;
          for((current_element=TAILQ_FIRST(&list)); current_element!=NULL;current_element = next_element){
			      next_element = TAILQ_NEXT(current_element, pointers);
            TAILQ_REMOVE(&list,&data[getpid],pointers);
          }
          TAILQ_INSERT_TAIL(&list,&data[getpid],pointers);//handover procedure
        }
      }
    }  
    current_time++;
  } 
  
  for(u32 i=0; i<size;++i){
    current_process = &data[i];
    total_response_time += current_process->response_time;
    total_waiting_time += current_process->waiting_time;
  }

  /* End of "Your code here" */

  printf("Average waiting time: %.2f\n", (float) total_waiting_time / (float) size);
  printf("Average response time: %.2f\n", (float) total_response_time / (float) size);

  free(data);
  return 0;
} 
